package reports.directors;

import hitExceptions.InvalidDataException;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import coreModel.HIT;
import coreModel.Item;
import coreModel.ItemManager;
import coreModel.Product;
import coreModel.ProductGroup;
import coreModel.ProductManager;
import coreModel.StorageUnit;
import coreModel.StorageUnitManager;
import coreModel.Supply;
import coreModel.Unit;
import reports.builders.IBuilder;




public class NMonthSupplyReport implements IDirector, IVisitor {

	private int months;
	
	private Collection< Collection<String> > pgStats;
	
	private enum UnitType {COUNT, VOLUME, MASS}
	
	public NMonthSupplyReport(int months)
	{
		this.months = months;
		
		pgStats = new ArrayList< Collection<String> >();
	}

	@Override
	public void build(IBuilder builder)
	{
		builder.addTitle(months + "-Month Supply Report");
		
			buildProductSupplyStats(builder);
			
			buildPGroupSupplyStats(builder);
		
		builder.saveToFile();
		
	}
	
	private void buildProductSupplyStats(IBuilder builder)
	{
		builder.addsubTitle("Products");
	
		
		ArrayList<String> headers = new ArrayList<String>();
		headers.add("Description");
		headers.add("Barcode");
		headers.add(months + "-Month Supply");
		headers.add("Current Supply");
		//added for pdf builder
		builder.startTable(headers.size());
		
		builder.addTableHeader(headers);
		
		Collection <Product> allProducts = 
			new ArrayList<Product>(HIT.getInstance().getProductManager().getAllProducts());
		Collections.sort((List<Product>)allProducts);
		
		for (Product p : allProducts)
		{
			processProduct(builder, p);
		}
		
		builder.endTable();
	}
	
	private void processProduct(IBuilder builder, Product p)
	{
		int threeMonthSupply = p.getThreeMonthSupply();
		float nMonthSupply = (float)threeMonthSupply*((float)months/(float)3);
		
		Collection<Item> itemsOfP = HIT.getInstance().getItemManager().getAllItemsOfProduct(p);	
		int nItemsOfProduct = (itemsOfP != null)? itemsOfP.size() : 0;
		
		
		if (nItemsOfProduct < nMonthSupply)
			builder.addRow( getRow(p, nMonthSupply) );
	}
	
	private Collection<String> getRow (Product p, float nMonthSupply)
	{
		String desc = p.getDescription();
		String bc = p.getBarcode().toString();
		
		int curSupply = 
			HIT.getInstance().getItemManager().getAllItemsOfProduct(p).size();
		
		DecimalFormat twoDec = new DecimalFormat("#.##");
		
		ArrayList<String> row = new ArrayList<String>();
		row.add(desc);
		row.add(bc);
		row.add( twoDec.format(nMonthSupply) + " COUNT" );
		row.add( twoDec.format(curSupply) + " COUNT" );
		
		return row;
	}
	
//	private Collection<String> getRow(Product p, float nMonthSupplyCount)
//	{
//		String desc = p.getDescription();
//		String bc = p.getBarcode().toString();
//		
//		float nMonthSupplyAmt = nMonthSupplyCount * (float)p.getSize();
//		String supplyUnit = p.getSupply().getSupplyUnit().toString();
//		
//		float curSupply = 
//		   p.getSize() * (float)HIT.getInstance().getItemManager().getAllItemsOfProduct(p).size();
//		
//		DecimalFormat twoDec = new DecimalFormat("#.##");
//		ArrayList<String> row = new ArrayList<String>();
//		row.add(desc);
//		row.add(bc);
//		row.add( twoDec.format(nMonthSupplyAmt) + " "+ supplyUnit );
//		row.add( twoDec.format(curSupply) + " "+ supplyUnit );
//		
//		return row;
//	}
	
//	private float counItems(Collection<Item> items, float size)
//	{
//		float sum=0;
//		
//		for(Item i : items)
//		{
//			sum = sum + size;
//		}
//		
//		return sum;
//	}
	
	private void buildPGroupSupplyStats(IBuilder builder)
	{
		builder.addsubTitle("Product Groups");
		
		
		ArrayList<String> headers = new ArrayList<String>();
		headers = new ArrayList<String>();
		headers.add("Product Group");
		headers.add("Storage Unit");
		headers.add(months + "-Month Supply");
		headers.add("Current Supply");
		
		//moved for pdf builder
		builder.startTable(headers.size());
		builder.addTableHeader(headers);
		
		
		StorageUnitManager suMgr = HIT.getInstance().getStorageUnitManager();
		suMgr.preOrderAccept(this);
		
		for (Collection<String> pg : pgStats)
			builder.addRow(pg);
		
		builder.endTable();
	}
	
	
	private StorageUnit curSU;
	@Override
	public void visit(ProductGroup productGroup)
	{
		String strPG = productGroup.getName();
		
		Supply threeMonths = productGroup.getThreeMonths();
		
		float nMonthSupply = threeMonths.getSupplySize() * ((float)months/(float)3);
		if (nMonthSupply == 0)
			return;
		
		
		float curSupplyAmt = calcPGCurSupply(productGroup, threeMonths.getSupplyUnit());
		if (curSupplyAmt > nMonthSupply)
			return;
		
		String strSU = curSU.getName();
		
		
		
		String supplyUnit = threeMonths.getSupplyUnit().toString();
		
		DecimalFormat twoDec = new DecimalFormat("#.##");
		String strNSup = twoDec.format(nMonthSupply) + " " + supplyUnit;
		String strCurSup = twoDec.format(curSupplyAmt) + " " + supplyUnit;
		
		ArrayList<String> row = new ArrayList<String>();
		row.add(strPG);
		row.add(strSU);
		row.add(strNSup);
		row.add(strCurSup);
		
		pgStats.add(row);
		
	}
	
	private float calcPGCurSupply(ProductGroup pg, Unit pgUnit)
	{
		// TODO - fix the toString on Unit Class
		

		float curPGSupply = 0;
		
		// sum up the immediate products
		ItemManager iMgr = HIT.getInstance().getItemManager();
		ProductManager pMgr = HIT.getInstance().getProductManager();
		
		Collection<Product> pgProducts = pMgr.getProductsOfContainer(pg);
		if (pgProducts != null)
			for (Product p : pgProducts)
			{
				if ( !compatibleUnits(p.getUnit(), pgUnit) )
					continue;
				
				float productSupply = calcProductCurSup(p, pg);
				
				productSupply = productSupply * getConversionRatio(p.getUnit(), pgUnit);
				curPGSupply = curPGSupply + productSupply;
			}
		
		// sum up the immediate product groups
		for (ProductGroup childPG : pg.getChildren())
		{
			Unit childUnit = childPG.getThreeMonths().getSupplyUnit();
			if ( !compatibleUnits(pgUnit, childUnit) )
				continue;
			
			float childPGSupply = calcPGCurSupply(childPG, pgUnit);
			curPGSupply = curPGSupply + childPGSupply; // * getConversionRatio(childUnit, pgUnit);
		}
		
		
		return curPGSupply;
	}
	
	public static float getConversionRatio(Unit src, Unit dest)
	{
		if (src == Unit.COUNT)
			return 1;
		
		if (src == dest)
			return 1;
		
		if (getUnitType(src) == UnitType.MASS)
			return massRatio(src, dest);
		else
			return volumeRatio(src, dest);
		
	}

	
	
	public static  float massRatio(Unit src, Unit dest)
	{
		double kg_lb = 2.20462262;
		double kg_oz = 35.2739619;
		double kg_g = 1000;
		
		double lb_oz = 16;
		double lb_g = 453.59237;
		double lb_kg = 1.0/kg_lb;
		
		double oz_g = 28.3495231;
		double oz_kg = 1.0/kg_oz;
		double oz_lb = 1.0/lb_oz;
		
		double g_kg = 1.0/kg_g;
		double g_lb = 1.0/lb_g;
		double g_oz = 1.0/oz_g;
		
		
		double ratio = 0;
		
		if (src == Unit.POUNDS)
		{
			if (dest == Unit.OUNCES)
				ratio = lb_oz;
			else if (dest == Unit.GRAMS)
				ratio = lb_g;
			else if (dest == Unit.KILOGRAMS)
				ratio = lb_kg;
		}
		else if (src == Unit.OUNCES)
		{
			if (dest == Unit.POUNDS)
				ratio = oz_lb;
			else if (dest == Unit.GRAMS)
				ratio = oz_g;
			else if (dest == Unit.KILOGRAMS)
				ratio = oz_kg;
		}
		else if (src == Unit.GRAMS)
		{
			if (dest == Unit.POUNDS)
				ratio = g_lb;
			else if (dest == Unit.OUNCES)
				ratio = g_oz;
			else if (dest == Unit.KILOGRAMS)
				ratio = g_kg;
		}
		else if (src == Unit.KILOGRAMS)
		{
			if (dest == Unit.POUNDS)
				ratio = kg_lb;
			else if (dest == Unit.OUNCES)
				ratio = kg_oz;
			else if (dest == Unit.GRAMS)
				ratio = kg_g;
		}
		
		return (float)ratio;
	}
	
	public static float volumeRatio(Unit src, Unit dest)
	{
		double ratio = 0;
		
		double gal_qt = 4;
		double gal_pt = 8;
		double gal_oz = 128;
		double gal_lit = 3.78541178;
		
		double qt_gal = 1.0/gal_qt;
		double qt_pt = 2;
		double qt_oz = 32;
		double qt_lit = 0.946352946;
		
		double pt_gal = 1.0/gal_pt;
		double pt_qt = 1.0/qt_pt;
		double pt_oz = 16;
		double pt_lit = 0.473176473;
		
		double oz_gal = 1.0/gal_oz;
		double oz_qt = 1.0/qt_oz;
		double oz_pt = 1.0/pt_oz;
		double oz_lit = 0.0295735296;
		
		double lit_gal = 1.0/gal_lit;
		double lit_qt = 1.0/qt_lit;
		double lit_pt = 1.0/pt_lit;
		double lit_oz = 1.0/oz_lit;
		
		if (src == Unit.GALLONS)
		{
			if (dest == Unit.QUARTS)
				ratio = gal_qt;
			else if (dest == Unit.PINTS)
				ratio = gal_pt;
			else if (dest == Unit.FLUID_OUNCES)
				ratio = gal_oz;
			else if (dest == Unit.LITERS)
				ratio = gal_lit;
		}
		else if (src == Unit.QUARTS)
		{
			if (dest == Unit.GALLONS)
				ratio = qt_gal;
			else if (dest == Unit.PINTS)
				ratio = qt_pt;
			else if (dest == Unit.FLUID_OUNCES)
				ratio = qt_oz;
			else if (dest == Unit.LITERS)
				ratio = qt_lit;
		}
		else if (src == Unit.PINTS)
		{
			if (dest == Unit.GALLONS)
				ratio = pt_gal;
			else if (dest == Unit.QUARTS)
				ratio = pt_qt;
			else if (dest == Unit.FLUID_OUNCES)
				ratio = pt_oz;
			else if (dest == Unit.LITERS)
				ratio = pt_lit;
		}
		else if (src == Unit.FLUID_OUNCES)
		{
			if (dest == Unit.GALLONS)
				ratio = oz_gal;
			else if (dest == Unit.QUARTS)
				ratio = oz_qt;
			else if (dest == Unit.PINTS)
				ratio = oz_pt;
			else if (dest == Unit.LITERS)
				ratio = oz_lit;
		}
		else if (src == Unit.LITERS)
		{
			if (dest == Unit.GALLONS)
				ratio = lit_gal;
			else if (dest == Unit.QUARTS)
				ratio = lit_qt;
			else if (dest == Unit.PINTS)
				ratio = lit_pt;
			else if (dest == Unit.FLUID_OUNCES)
				ratio = lit_oz;
		}

		return (float)ratio;
	}
	
	private float calcProductCurSup(Product p, ProductGroup pg)
	{
		Collection <Item> allItems =
			HIT.getInstance().getItemManager().getItemsByContainerAndProduct(pg, p);
		
		float totalSize = (float)allItems.size() * p.getSize();
		
		return totalSize;
	}
	
	private boolean compatibleUnits(Unit u1, Unit u2)
	{		
		return getUnitType(u1) == getUnitType(u2);
	}
	
	
	public static UnitType getUnitType(Unit u)
	{
		switch (u)
		{
			case COUNT:
				return UnitType.COUNT;
			case POUNDS:
			case OUNCES:
			case GRAMS:
			case KILOGRAMS:
				return UnitType.MASS;
			case GALLONS:
			case QUARTS:
			case PINTS:
			case FLUID_OUNCES:
			case LITERS:
			default:
				return UnitType.VOLUME;
		}
		
	}
	
	private Supply calcNSupply(Supply sup3mo)
	{
		float ratio = sup3mo.getSupplySize()/(float)3;
		float newsize = (float)months * ratio;
		
		Supply nsup;
		
		try
		{	nsup = new Supply(newsize, sup3mo.getSupplyUnit());	} 
		catch (InvalidDataException e)
		{	nsup = null; e.printStackTrace();	}
		
		return nsup;
	}
	
	
	
	@Override
	public void visit(StorageUnit storageUnit)
	{
		curSU = storageUnit;
	}

	@Override
	public void visit(StorageUnitManager suManager)
	{
		
	}

	
}


